import typing
import numpy
from yiqiu_core.Chess import Chess
from yiqiu_core.Position import ChessPosition
from queue import Queue


class CheckStatusUsingRule:
    CanNotPlace = 0
    Success_and_Continue = 1
    Success_and_Win = 2     # 落子成功，游戏胜利（新落子的这一方）
    Success_and_Tie = 3     # 落子成功，达成平局
    Success_and_Lose = 4

    def GetType(self, index):
        if index == 0:
            return 'CanNotPlace'
        elif index == 1:
            return 'Success_and_Continue'
        elif index == 2:
            return 'Success_and_Win'
        elif index == 3:
            return 'Success_and_Tie'
        elif index == 4:
            return 'Success_and_Lose'


class Rule(object):
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = object.__new__(cls)
        return cls._instance

    def __init__(self):
        pass

    def CheckBoard(self, board_array: numpy.ndarray, new_position: ChessPosition, new_chess: int):
        # new_chess 中 整数定义见 Chess 枚举类
        # 检查该落子点会造成什么后果
        new_board_array = board_array.copy()
        new_board_array[new_position.row_num][new_position.col_num] = new_chess
        # 返回后果 + 处理后的棋盘状态
        # 例：
        return CheckStatusUsingRule.Success_and_Win, new_board_array

    def _is_in_range(self, position: ChessPosition, row_num, col_num):
        if position.row_num > 0 and position.row_num < row_num and position.col_num > 0 and position.col_num < col_num:
            return True
        else:
            return False


class Wuzi_Rule(Rule):
    def CheckBoard(self, board_array: numpy.ndarray, new_position: ChessPosition, new_chess: int):
        if new_position.row_num >= board_array.shape[0] or\
                new_position.row_num < 0 or\
                new_position.col_num >= board_array.shape[1] or\
                new_position.col_num < 0:
            print('wuzi_cannot_place_1')
            return CheckStatusUsingRule.CanNotPlace, board_array
        if board_array[new_position.row_num][new_position.col_num] != Chess.Empty:
            print('wuzi_cannot_place_2')
            return CheckStatusUsingRule.CanNotPlace, board_array

        board_array[new_position.row_num][new_position.col_num] = new_chess
        # 搜索，检查是否连成五子
        # 四个方向，横竖加两条斜线
        directions = [
            [-1, -1], [-1, 0], [-1, 1], [0, -1]
        ]
        GameOver = False
        for d in directions:
            cnt = 1  # 新落的这个子要算一个
            p = new_position
            while board_array[p.row_num][p.col_num] == new_chess:
                p = ChessPosition(
                    p.row_num + d[0],
                    p.col_num + d[1]
                )
                if p.row_num >= 0 and p.row_num < board_array.shape[0] and\
                    p.col_num >= 0 and p.col_num < board_array.shape[1] and\
                        board_array[p.row_num][p.col_num] == new_chess:
                    cnt += 1
                else:
                    break
            # 换一个方向接着查，注意这个方向和上一个方向其实是一条直线
            p = new_position
            while board_array[p.row_num][p.col_num] == new_chess:
                p = ChessPosition(
                    p.row_num - d[0],
                    p.col_num - d[1]
                )
                if p.row_num >= 0 and p.row_num < board_array.shape[0] and\
                    p.col_num >= 0 and p.col_num < board_array.shape[1] and\
                        board_array[p.row_num][p.col_num] == new_chess:
                    cnt += 1
                else:
                    break
            if cnt >= 5:
                GameOver = True
                break

        if GameOver:
            return CheckStatusUsingRule.Success_and_Win, board_array
        else:
            Tie = True
            for i in range(board_array.shape[0]):
                for j in range(board_array.shape[1]):
                    if board_array[i][j] == Chess.Empty:
                        Tie = False
                        break
            if Tie:
                return CheckStatusUsingRule.Success_and_Tie, board_array
            else:
                return CheckStatusUsingRule.Success_and_Continue, board_array


class Go_Rule(Rule):
    directions = [
        [-1, 0],
        [0, -1], [0, 1],
        [1, 0],
    ]

    def Alive(self, board_array: numpy.ndarray, position_to_check: ChessPosition):
        # 从某个位置开始 bfs 搜索，检查该区域的子是否还有气
        if not self._is_in_range(position_to_check, board_array.shape[0], board_array.shape[1]):
            raise Exception("position not in range!")
        else:
            chess_type = board_array[position_to_check.row_num][position_to_check.col_num]
            if chess_type == Chess.Empty:
                return True, set()
            bfs_q = Queue()
            bfs_q.put(position_to_check)
            bfs_set = set()
            bfs_set.add(position_to_check)
            AliveStatus = False
            while not bfs_q.empty():
                p_in_bfs = bfs_q.get()
                for d2 in self.directions:
                    new_p = ChessPosition(
                        p_in_bfs.row_num + d2[0],
                        p_in_bfs.col_num + d2[1]
                    )
                    if new_p not in bfs_set and self._is_in_range(new_p, board_array.shape[0], board_array.shape[1]):
                        if board_array[new_p.row_num][new_p.col_num] == Chess.Empty:
                            AliveStatus = True
                            break
                        if board_array[new_p.row_num][new_p.col_num] == chess_type:
                            bfs_q.put(new_p)
                            bfs_set.add(new_p)
            return AliveStatus, bfs_set

    def CheckBoard(self, board_array: numpy.ndarray, new_position: ChessPosition, new_chess: int):
        if new_position.row_num >= board_array.shape[0] or\
                new_position.row_num < 0 or\
                new_position.col_num >= board_array.shape[1] or\
                new_position.col_num < 0:
            print('go_cannot_place_1')
            return CheckStatusUsingRule.CanNotPlace, board_array
        if board_array[new_position.row_num][new_position.col_num] != Chess.Empty:
            print('go_cannot_place_2')
            return CheckStatusUsingRule.CanNotPlace, board_array

        # 先把该子落下，检查是否存在提子
        board_array[new_position.row_num][new_position.col_num] = new_chess
        CanTakeAway = False
        for d in self.directions:
            p = ChessPosition(
                new_position.row_num + d[0],
                new_position.col_num + d[1]
            )
            if self._is_in_range(p, board_array.shape[0], board_array.shape[1]):
                if not self.Alive(board_array, p)[0]:
                    CanTakeAway = True
                    break

        # 若不存在提子
        if not CanTakeAway:
            # 则 1.需检查该落子是否合法（落完子之后该子是否有气）
            if not self.Alive(board_array, new_position)[0]:
                board_array[new_position.row_num][new_position.col_num] = Chess.Empty
                print('go_cannot_place_3')
                return CheckStatusUsingRule.CanNotPlace, board_array
            # 2.检查是否达到终局 [TODO]
            # 3.返回棋盘
            return CheckStatusUsingRule.Success_and_Continue, board_array
        else:
            for d in self.directions:
                p = ChessPosition(
                    new_position.row_num + d[0],
                    new_position.col_num + d[1]
                )
                if self._is_in_range(p, board_array.shape[0], board_array.shape[1]):
                    alive, chesses_to_take = self.Alive(board_array, p)
                    if not alive:
                        for chess in chesses_to_take:
                            board_array[chess.row_num][chess.col_num] = Chess.Empty
            return CheckStatusUsingRule.Success_and_Continue, board_array


Rule_Dict: typing.Dict[str, Rule] = {
    "standard_wuzi": Wuzi_Rule(),
    "standard_go": Go_Rule(),
}
